package agents;

import jade.core.Agent;
import jade.core.behaviours.*;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;

import java.util.*;

import javax.swing.JOptionPane;

import statics.TradingConst;
import gui.HomeGUI;
import gui.RetailerGUI;

public class RetailerAgent extends TickListenerAgent
{
	transient protected RetailerGUI myUI;	
	
	static final int[] powerArr = {30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000, 30000};
	
	private final float MIN_PRICE = 0.12f;
	private final float MAX_PRICE = 0.25f;	
	private final int SALES_RECORDS = 5;
		
	private float price;
	private List<Float>[] priceList = new ArrayList[12];
	private float[] priceAvg = new float[12];
	private double powerNow;
	private double powerSupplied;
	private int requestTickCount;
	private int salesTickCount;
	private List<Float>[] salesList = new ArrayList[12];
	private float[] salesPercentAvg = new float[12];
	private float MarketShareExpected = 0.33f;	
	private int time;
		
	protected int TicksToUpdatePrice = Math.max((int) (TradingConst.RETAILER_AGENT_MIN_TO_UPDATE_PRICE*TradingConst.SEC_IN_MIN*TradingConst.SEC_TO_MS
			/(TradingConst.Tick_TO_MS*TradingConst.ACCELERATION_RATE)),1);
	protected int TicksToGeneratePower = Math.max((int) (TradingConst.RETAILER_AGENT_MIN_TO_GENERATE_POWER*TradingConst.SEC_IN_MIN*TradingConst.SEC_TO_MS
			/(TradingConst.Tick_TO_MS*TradingConst.ACCELERATION_RATE)),1);
		
	// Put agent initializations here
	protected void setup() 
	{
		super.setup();
		setupPricing();
		setupSales();
		
		time = 0;
		powerNow = powerArr[time];
		requestTickCount = 0;
		salesTickCount = 0;
			
		Float[] args = (Float[])getArguments();
		if(args != null
				&&args.length>0)
		{
			MarketShareExpected = args[0];
		}
		// Register the book-selling service in the yellow pages
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID());
		ServiceDescription sd = new ServiceDescription();
		sd.setType("power-retailer");
		sd.setName("JADE-power-trading");
		dfd.addServices(sd);
		try {
			DFService.register(this, dfd);
		}
		catch (FIPAException fe) {
			fe.printStackTrace();
		}
		
		addBehaviour(new OfferRequestsServer());
		addBehaviour(new PurchaseOrdersServer());
		
		myUI = new RetailerGUI(this);
		myUI.setVisible(true);
	}
		
	void setupPricing()
	{
		Random rand = new Random();
		float r = 0.00f;
		
		for (int i = 0; i < priceList.length; i++)
		{
			priceList[i] = new ArrayList<Float>();
			priceAvg[i] = 0;
			for (int j = 0; j < SALES_RECORDS; j ++)
			{
				//r = rand.nextFloat() * (MAX_PRICE - MIN_PRICE*1.7f) + MIN_PRICE*1.7f;
				r =0.2f;
				priceList[i].add(r);
				priceAvg[i] += r;
			}
			priceAvg[i] = priceAvg[i]/priceList[i].size();
		}
		price = 0.2f;
	}
		
	void setupSales()
	{
		for (int i = 0; i < salesPercentAvg.length; i++)
		{
			salesPercentAvg[i] = MarketShareExpected;
			salesList[i] = new ArrayList<Float>();
			for (int j = 0; j < SALES_RECORDS; j++)
				salesList[i].add(MarketShareExpected);
		}
	}
	
	@Override
	protected void OnTickAction() 
	{
		// TODO Auto-generated method stub
		if(CurrentTickCount%TicksToUpdatePrice == 0)
		{
			addBehaviour(new UpdatePriceBehaviour());
			
		}		
		if(CurrentTickCount%TicksToGeneratePower == 0)
			addBehaviour(new GeneratePowerBehaviour());
	}
		
	// Put agent clean-up operations here
	protected void takeDown() 
	{
		// Deregister from the yellow pages
		try {
			DFService.deregister(this);
		}
		catch (FIPAException fe) {
			fe.printStackTrace();
		}
		// Printout a dismissal message
		//System.out.println("Retailer-agent "+getAID().getName()+" terminating.");
	}
		
	private class OfferRequestsServer extends CyclicBehaviour 
	{
		public void action() 
		{				
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.CFP);
			ACLMessage msg = myAgent.receive(mt);
			if (msg != null) 
			{
				// CFP Message received. Process it
				String powerRequired = msg.getContent();
				ACLMessage reply = msg.createReply();
				Double powerReqDouble = Double.valueOf(powerRequired);
		
				if ((powerReqDouble > 0) && (powerNow > powerReqDouble)) 
				{
					// The requested power is available for sale. Reply with the price
					reply.setPerformative(ACLMessage.PROPOSE);
					reply.setContent(String.valueOf(price));						
					requestTickCount++;
				}
				else if ((powerReqDouble < 0) && (powerNow + Math.abs(powerReqDouble) < powerSupplied * 1.1))
				{
					reply.setPerformative(ACLMessage.PROPOSE);
					reply.setContent(String.valueOf( (price*2.0)/3.0 ));
				}
				else 
				{
					// The requested power is NOT available for sale.
					reply.setPerformative(ACLMessage.REFUSE);
					reply.setContent("not-available");
				}
				myAgent.send(reply);
			}
			else {
				block();
			}
		}
	}  // End of inner class OfferRequestsServer
		
	/**
	   Inner class PurchaseOrdersServer.
	   This is the behaviour used by Book-seller agents to serve incoming 
	   offer acceptances (i.e. purchase orders) from buyer agents.
	   The seller agent removes the purchased book from its catalogue 
	   and replies with an INFORM message to notify the buyer that the
	   purchase has been successfully completed.
	 */
	private class PurchaseOrdersServer extends CyclicBehaviour 
	{
		public void action() 
		{
			MessageTemplate mt = MessageTemplate.MatchPerformative(ACLMessage.ACCEPT_PROPOSAL);
			ACLMessage msg = myAgent.receive(mt);
			if (msg != null) 
			{
				// ACCEPT_PROPOSAL Message received. Process it
				String powerRequired = msg.getContent();
				ACLMessage reply = msg.createReply();
				Double powerReqDouble = Double.valueOf(powerRequired);

				if ((powerReqDouble > 0) && (powerNow > powerReqDouble)) 
				{
					reply.setPerformative(ACLMessage.INFORM);
					//System.out.println(powerRequired+" sold to agent "+msg.getSender().getName());
					powerNow -= powerReqDouble;
					//JOptionPane.showMessageDialog(null, "SOLD." + powerReqDouble + " " + powerNow, "Warning " +getAID().getName(), JOptionPane.WARNING_MESSAGE);
					salesTickCount++;
				}
				else if ((powerReqDouble < 0) && (powerNow + powerReqDouble < powerSupplied * 1.1))
				{
					reply.setPerformative(ACLMessage.INFORM);
					//System.out.println(-powerReqDouble+" bought from agent "+msg.getSender().getName());
					powerNow -= powerReqDouble;
					
				}
				else 
				{
					// The requested book has been sold to another buyer in the meanwhile .
					reply.setPerformative(ACLMessage.FAILURE);
					reply.setContent("not-available");
					//price = MAX_PRICE;
				}
				myAgent.send(reply);
			}
			else {
				block();
			}
		}
	}  // End of inner class OfferRequestsServer
		
	private class GeneratePowerBehaviour extends OneShotBehaviour
	{
		public void action() 
		{
			time += 1;
			time = time % 12;
			
			powerSupplied = powerArr[time];
			powerNow = powerSupplied;
			
			salesTickCount = 0;
			requestTickCount = 0;
			
		}
	}
		
	private class UpdatePriceBehaviour extends OneShotBehaviour
	{
		@Override
		public void action() 
		{
			float salesPercent = 0.0f;
			
			if (requestTickCount > 0)
				salesPercent = ((float)salesTickCount/(float)requestTickCount);
			else 
				return; //no request // don't do anything
			float weightedSaleData = (salesPercent*0.5f + salesPercentAvg[time]*0.5f);
			//float weightedSaleData = salesPercent;
//			if (salesPercent > salesPercentAvg[time])
//				price += (powerNow/Math.pow(powerSupplied, 1.5)) + 0.05;
//			else if (salesPercent < salesPercentAvg[time])
//				price -= price/4 * (salesPercentAvg[time] - salesPercent);
			if (weightedSaleData > MarketShareExpected)
				price += Math.min((powerSupplied/Math.pow(powerNow, 1.5)), 0.05) * (weightedSaleData - MarketShareExpected);
			else if ( weightedSaleData < MarketShareExpected)
				price -= (Math.max(price/8 * (MarketShareExpected - weightedSaleData), 0.005f));
			
			if (price > MAX_PRICE)
				price = MAX_PRICE;
			else if (price < MIN_PRICE)
				price = MIN_PRICE;
			
			salesList[time].remove(0);
			salesList[time].add(salesPercent);
			
			salesPercentAvg[time] = 0.00f;
			for (int i = 0; i < salesList[time].size(); i++)
				salesPercentAvg[time] += salesList[time].get(i);
			salesPercentAvg[time] = salesPercentAvg[time]/salesList[time].size();
			
			price = (price*0.8f + priceAvg[time]*0.2f);
			
			priceList[time].remove(0);
			priceList[time].add(price);
			
			priceAvg[time] = 0.00f;
			for (int i = 0; i < priceList[time].size(); i++)
				priceAvg[time] += priceList[time].get(i);
			priceAvg[time] = priceAvg[time]/priceList[time].size();
			
			price = (price*0.5f + priceAvg[time]*0.5f);
			
			myUI.UpdateFromRetailerAgent(CurrentTickCount, price, powerNow, powerSupplied, salesTickCount, requestTickCount);
		}
	}
		
	public float getCurrentPrice()
	{
		return price;
	}
	
	public float getCurrentPriceAverage()
	{
		return priceAvg[time];
	}
	
	public double getPowerNow()
	{
		return powerNow;
	}
	
	public double getPowerSupplied()
	{
		return powerSupplied;
	}
	
	public double getSalesTarget()
	{
		return MarketShareExpected;
	}
	
	public void setMarketShareExpected(double share)
	{
		MarketShareExpected = (float) share;
	}

}
